﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.Xml;
using System.ServiceModel.Syndication;
using System.Web;

namespace Knowledgecast.Twitter
{
    public class TwitterApi
    {
        public TwitterApi(string user, string password)
        {
            Username = user;
            Password = password;
        }

        const string FriendsTimelineRestUrl = "http://twitter.com/statuses/friends_timeline.xml";
        const string RepliesRestUrl = "http://twitter.com/statuses/mentions.xml";
        const string SearchFeedUrlFragment = "http://search.twitter.com/search.atom?q=";
        const string UpdateStatusUrlFragment = "http://twitter.com/statuses/update.xml?status=";

        public string Username { get; set; }
        public string Password { get; set; }
        protected string LatestFriendsTimelineMessageId { get; set; }
        protected string LatestReplyMessageId { get; set; }

        #region Get Friends Timeline
        public void GetFriendsTimelineAsync()
        {
            HttpWebRequest request = null;

            try
            {
                if (String.IsNullOrEmpty(LatestFriendsTimelineMessageId))
                {
                    Debug("Requesting statuses");
                    request = SetupWebRequest(FriendsTimelineRestUrl);
                }
                else
                {
                    Debug("Requesting statuses since " + LatestFriendsTimelineMessageId);
                    request = SetupWebRequest(FriendsTimelineRestUrl + "?since_id=" + LatestFriendsTimelineMessageId);
                }

                request.BeginGetResponse(new AsyncCallback(GetFriendsTimelineResponse), request);
            }
            catch (Exception ex)
            {
                Debug("GetFriendsTimelineAsync crashed: " + ex.Message + ": " + ex.StackTrace);
                if (ex is WebException)
                {
                    Debug("WebException response:");
                    using (StreamReader sr = new StreamReader((ex as WebException).Response.GetResponseStream()))
                    {
                        Trace.WriteLine(sr.ReadToEnd());
                    }
                }
            }
            finally
            {
                if (request != null)
                {
                    request = null;
                }
            }
        }

        void GetFriendsTimelineResponse(IAsyncResult result)
        {
            HttpWebRequest request = result.AsyncState as HttpWebRequest;
            using (StreamReader sr = new StreamReader(request.EndGetResponse(result).GetResponseStream()))
            {
                Debug("Got statuses");
                ObservableCollection<Status> statusList = ReadStatuses(sr.ReadToEnd());
                if (statusList != null)
                {
                    Debug("Found " + statusList.Count + " updates");
                    if (statusList.Count > 0)
                    {
                        LatestFriendsTimelineMessageId = statusList[0].ID;
                    }

                    if (GetFriendsTimelineCompleted != null)
                    {
                        GetFriendsTimelineCompleted(statusList);
                    }
                }
            }
        }

        public event UpdateStatusDelegate GetFriendsTimelineCompleted;
        #endregion

        #region Get Replies
        public void GetRepliesAsync()
        {
            HttpWebRequest request = null;

            try
            {
                if (String.IsNullOrEmpty(LatestReplyMessageId))
                {
                    Debug("Requesting replies");
                    request = SetupWebRequest(RepliesRestUrl);
                }
                else
                {
                    Debug("Requesting replies since " + LatestReplyMessageId);
                    request = SetupWebRequest(RepliesRestUrl + "?since_id=" + LatestReplyMessageId);
                }

                request.BeginGetResponse(new AsyncCallback(GetRepliesTimelineResponse), request);
            }
            catch (Exception ex)
            {
                Debug("GetRepliesAsync crashed: " + ex.Message + ": " + ex.StackTrace);
                if (ex is WebException)
                {
                    Debug("WebException response:");
                    using (StreamReader sr = new StreamReader((ex as WebException).Response.GetResponseStream()))
                    {
                        Trace.WriteLine(sr.ReadToEnd());
                    }
                }
            }
            finally
            {
                if (request != null)
                {
                    request = null;
                }
            }
        }

        void GetRepliesTimelineResponse(IAsyncResult result)
        {
            HttpWebRequest request = result.AsyncState as HttpWebRequest;
            using (StreamReader sr = new StreamReader(request.EndGetResponse(result).GetResponseStream()))
            {
                Debug("Got replies");
                ObservableCollection<Status> statusList = ReadStatuses(sr.ReadToEnd());
                if (statusList != null)
                {
                    Debug("Found " + statusList.Count + " replies");
                    if (statusList.Count > 0)
                    {
                        LatestReplyMessageId = statusList[0].ID;
                    }

                    if (GetRepliesCompleted != null)
                    {
                        GetRepliesCompleted(statusList);
                    }
                }
            }
        }

        public event UpdateStatusDelegate GetRepliesCompleted;
        #endregion

        #region Get Search Results
        public ObservableCollection<Status> GetSearchResults(string query)
        {
            ObservableCollection<Status> results = null;
            try
            {
                using (XmlReader reader = XmlReader.Create(SearchFeedUrlFragment + query))
                {
                    Atom10FeedFormatter atom = new Atom10FeedFormatter();
                    if (atom.CanRead(reader))
                    {
                        atom.ReadFrom(reader);
                        var items = from item in atom.Feed.Items.OfType<SyndicationItem>()
                                    select new Status
                                    {
                                        ID = item.Id,
                                        CreatedAt = item.PublishDate.ToLocalTime().DateTime,
                                        Text = item.Title.Text,
                                        Source = "Search",

                                        User = new TwitterUser
                                        {
                                            ScreenName = item.Authors[0].Name,
                                            Url = item.Authors[0].Uri,
                                            ProfileImageUrl = item.Links[1].Uri.ToString()
                                        }
                                    };


                        results = new ObservableCollection<Status>(items);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug("GetSearchResults crashed: " + ex.Message + ": " + ex.StackTrace);
            }

            return results;
        }
        #endregion

        #region Update Status
        public void UpdateStatus(string update)
        {
            UpdateStatus(update, null);
        }

        public void UpdateStatus(string update, Status replyTo)
        {
            HttpWebRequest request = null;
            try
            {
                string auth = Username + ":" + Password;
                auth = Convert.ToBase64String(Encoding.Default.GetBytes(auth));

                string url = UpdateStatusUrlFragment + HttpUtility.UrlEncode(update);
                if (replyTo != null)
                {
                    url += "&in_reply_to_status_id=" + replyTo.ID;
                }

                request = (HttpWebRequest)WebRequest.Create(url);
                request.Timeout = 5000;
                request.Method = "POST";
                request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + auth);
                request.GetResponse();
            }
            catch (Exception ex)
            {
                Debug("UpdateStatus crashed: " + ex.Message + ": " + ex.StackTrace);
                throw new ApplicationException("Send failed", ex);
            }
        }
        #endregion

        #region Private Methods
        HttpWebRequest SetupWebRequest(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "GET";
            request.Timeout = 5000;
            string auth = Username + ":" + Password;
            auth = Convert.ToBase64String(Encoding.Default.GetBytes(auth));
            request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + auth);

            return request;
        }

        private ObservableCollection<Status> ReadStatuses(string response)
        {
            StringReader reader = new StringReader(response);
            XDocument doc = XDocument.Load(reader);
            reader.Close();
            reader.Dispose();

            var statuses = from status in doc.Descendants(XName.Get("status"))
                           orderby long.Parse(status.Element(XName.Get("id")).Value) descending
                           select new Status
                           {
                               CreatedAt = UTCTimestampConverter.ConvertFromString(status.Element(XName.Get("created_at")).Value),
                               ID = status.Element(XName.Get("id")).Value,
                               Text = status.Element(XName.Get("text")).Value,
                               Source = status.Element(XName.Get("source")).Value,
                               Truncated = bool.Parse(status.Element(XName.Get("truncated")).Value),
                               InReplyToStatus = status.Element(XName.Get("in_reply_to_status_id")).Value,
                               InReplyToUser = status.Element(XName.Get("in_reply_to_user_id")).Value,
                               Favourited = bool.Parse(status.Element(XName.Get("favorited")).Value),
                               InReplyToScreenName = status.Element(XName.Get("in_reply_to_screen_name")).Value,

                               User = new TwitterUser
                               {
                                   ID = status.Element(XName.Get("user")).Element(XName.Get("id")).Value,
                                   Name = status.Element(XName.Get("user")).Element(XName.Get("name")).Value,
                                   ScreenName = status.Element(XName.Get("user")).Element(XName.Get("screen_name")).Value,
                                   Location = status.Element(XName.Get("user")).Element(XName.Get("location")).Value,
                                   Description = status.Element(XName.Get("user")).Element(XName.Get("description")).Value,
                                   ProfileImageUrl = status.Element(XName.Get("user")).Element(XName.Get("profile_image_url")).Value,
                                   Url = status.Element(XName.Get("user")).Element(XName.Get("url")).Value,
                                   Protected = bool.Parse(status.Element(XName.Get("user")).Element(XName.Get("protected")).Value),
                                   FollowersCount = int.Parse(status.Element(XName.Get("user")).Element(XName.Get("followers_count")).Value),
                               }
                           };

            ObservableCollection<Status> statusList = new ObservableCollection<Status>(statuses);
            return statusList;
        }

        void Debug(string message)
        {
            string debugMessage = "[" + DateTime.Now.ToShortDateString() + DateTime.Now.ToShortTimeString() + "] - TwitterApi";
            debugMessage += ": " + message;
            Trace.WriteLine(debugMessage);
        }
        #endregion
    }

    public delegate void UpdateStatusDelegate(ObservableCollection<Status> statuses);
}
